package de.pa2.proxy;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.pa2.proxy.config.ServerConfiguration;

public class ProxyRequestListenerThread extends Thread {
	private static Logger LOG = LoggerFactory
			.getLogger(ProxyRequestListenerThread.class);
	private final ServerSocket serversocket;
	private final HttpParams params;
	private final HttpService httpService;

	public ProxyRequestListenerThread(int port,
			ServerConfiguration configuration) throws IOException {
		this.serversocket = new ServerSocket(port);
		this.params = new SyncBasicHttpParams();
		this.params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
				.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE,
						8 * 1024).setBooleanParameter(
						CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
				.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
				.setParameter(CoreProtocolPNames.ORIGIN_SERVER,
						"HttpComponents/1.1");

		// Set up HTTP protocol processor for incoming connections
		HttpProcessor inhttpproc = new ImmutableHttpProcessor(
				new HttpRequestInterceptor[] { new RequestContent(),
						new RequestTargetHost(), new RequestConnControl(),
						new RequestUserAgent(), new RequestExpectContinue() });

		// Set up HTTP protocol processor for outgoing connections
		HttpProcessor outhttpproc = new ImmutableHttpProcessor(
				new HttpResponseInterceptor[] { new ResponseDate(),
						new ResponseServer(), new ResponseContent(),
						new ResponseConnControl() });

		// Set up outgoing request executor
		HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

		// Set up incoming request handler
		HttpRequestHandlerRegistry registry = new HttpRequestHandlerRegistry();
		// reqistry.register("*", new ProxyHandler(this.target, outhttpproc,
		// httpexecutor));
		registry.register("*", new ProxyHandler(outhttpproc, httpexecutor,
				configuration));
		// Set up the HTTP service
		this.httpService = new HttpService(inhttpproc,
				new DefaultConnectionReuseStrategy(),
				new DefaultHttpResponseFactory(), registry, this.params);
	}

	public void run() {
		LOG.debug("run: " + "Listening on port "
				+ this.serversocket.getLocalPort());
		while (!Thread.interrupted()) {
			try {
				// Set up incoming HTTP connection
				Socket insocket = this.serversocket.accept();
				DefaultHttpServerConnection inconn = new DefaultHttpServerConnection();
				LOG.debug("run: " + "Incoming connection from "
						+ insocket.getInetAddress());
				inconn.bind(insocket, this.params);
				Thread t = new ProxyThread(this.httpService, inconn);
				t.setDaemon(true);
				t.start();
			} catch (InterruptedIOException ex) {
				break;
			} catch (IOException e) {
				LOG.error("I/O error initialising connection thread: "
						+ e.getMessage(), e);
				break;
			}
		}
	}
}
